/*
 * Luna Driver Library. Used to access the Luna driver board via HID.
 *
 * Copyright (c) 2010-2012 Robo Dynamics
 *
 * Author: Tyler W. Wilson
 *
 */

/* Verison history:
 *  08.10.2012: Removed legacy API and renamed everything to Luna.
 *  30.12.2010: Fixed the GetSensors and SetDrive bufer size to eliminate CRC failue
 */

//#define USE_HIDAPI
#define USE_LIBUSB
//#define USE_LIBUSBX

#include "Luna_Library.h"

#if defined(USE_LIBUSBX)
	#define USE_LIBUSB 1
	#include <libusbx-1.0/libusb.h>
#endif
#if defined(USE_LIBUSB)
	#include <libusb-1.0/libusb.h>
#endif
#if defined(USE_HIDAPI)
	#include "hidapi/hidapi.h"
#endif

// Standard system includes
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


// Luna-specific constants
const unsigned short VID = 0x10C4;
const unsigned short PID = 0xBEEF;	// Ought to get get a proper PID from SiLabs

// Report IDs
#define CMD_FIRMWARE_VERSION 0x05
#define CMD_SENSORS          0x0C
#define CMD_DRIVE            0x0D

// HID constants
#define HID_GET_REPORT   0x01
#define HID_GET_IDLE     0x02
#define HID_GET_PROTOCOL 0x03
#define HID_SET_REPORT   0x09
#define HID_SET_IDLE     0x0A
#define HID_SET_PROTOCOL 0x0B

#define HID_REPORT_TYPE_INPUT   0x01
#define HID_REPORT_TYPE_OUTPUT  0x02
#define HID_REPORT_TYPE_FEATURE 0x03

// local constants
const unsigned int TIMEOUT = 5000;

#if defined(USE_LIBUSB)
	static struct libusb_device_handle* devh = 0;
#endif

#if defined(USE_HIDAPI)
	static hid_device *devh = 0;
#endif


// Initialize any libraries we use from here
//
LUNA_DLL_API int Luna_Init(void)
{
	int status = 0;

#if defined(USE_LIBUSB)
	status = libusb_init(0);

	if (status < 0)
	{
		return status;
	}

	// set up debugging while we are...debugging
	libusb_set_debug(0, 3);
#endif

#if defined(USE_HIDAPI)
	status = 0;
#endif

	return status;
}

LUNA_DLL_API int Luna_Open(void** handle)
{
	int status = 0;

#if defined(USE_LIBUSB)
	devh = libusb_open_device_with_vid_pid(0, VID, PID);

	if (devh == 0)
	{
		// nothing else to do
		*handle = 0;
		return -1;
	}

	libusb_device* dev = libusb_get_device(devh);

	// verify we are talking to a reasonable device: check descriptors, etc.
	libusb_device_descriptor dev_desc;
	libusb_get_device_descriptor(dev, &dev_desc);
	printf("Vendor: 0x%x\n", dev_desc.idVendor);

	// only really does anything on Linux
	libusb_claim_interface(devh, 0);
	// if this fails, detach and try again
	libusb_detach_kernel_driver(devh, 0);

	//libusb_set_configuration(devh, 0);

	*handle = (void*)devh;

	status = devh ? 0 : -5; //-EIO;
#endif

#if defined(USE_HIDAPI)
	*handle = devh = hid_open(VID, PID, 0);

	status = devh ? 0 : -1;
#endif

	return status;
}

LUNA_DLL_API int Luna_GetVersion(void* handle, Luna_Version* version)
{
	int status = 1;

	// Use the CMD_FIRMWARE_VERSION Report to get the two version bytes
	unsigned char buffer[9];
	buffer[0] = CMD_FIRMWARE_VERSION;

#if defined(USE_LIBUSB)
	status = libusb_control_transfer(devh, LIBUSB_ENDPOINT_IN|LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE, HID_GET_REPORT,
		(HID_REPORT_TYPE_INPUT<<8)|CMD_FIRMWARE_VERSION, 0, buffer, sizeof(buffer), TIMEOUT);
#endif

#if defined(USE_HIDAPI)
	status = hid_get_input_report(devh, buffer, sizeof(buffer));
#endif

	// check the data, and return the BCD version
	if (status >= 0 && version != 0)
	{
		version->major = buffer[1];
		version->minor = buffer[2];
	}

	return status;
}

LUNA_DLL_API int Luna_GetSensors(void* handle, Luna_Sensors* sensors)
{
	int status = 0;

	// Use the CMD_SENSORS report to get the sensor values

	unsigned char buffer[33];
	memset(buffer, 0, sizeof(buffer));
	buffer[0] = CMD_SENSORS;

#if defined(USE_LIBUSB)
	// make the request for the sensor data
	status = libusb_control_transfer(devh, LIBUSB_ENDPOINT_IN|LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE, HID_GET_REPORT,
		(HID_REPORT_TYPE_INPUT<<8)|CMD_SENSORS, 0, buffer, sizeof(buffer), TIMEOUT);
#endif

#if defined(USE_HIDAPI)
	status = hid_get_input_report(devh, buffer, sizeof(buffer));
#endif

	if (status >= 0 && sensors != 0)
	{
		//printf("CMD_SENSORS returned %d bytes", status);

		// fill in the data structure, piece by piece to minimize errors
		sensors->sonar[0] = buffer[1] | (buffer[2] << 8);
		sensors->sonar[1] = buffer[3] | (buffer[4] << 8);
		sensors->sonar[2] = buffer[5] | (buffer[6] << 8);
		sensors->bumper = buffer[7];
		sensors->charger = buffer[8];
		sensors->battery = buffer[9];
		sensors->right_distance = buffer[10] | (buffer[11] << 8);
		sensors->left_distance = buffer[12] | (buffer[13] << 8);
		sensors->right_speed = buffer[14] | (buffer[15] << 8);
		sensors->left_speed = buffer[16] | (buffer[17] << 8);
	}

	return status;
}

LUNA_DLL_API int Luna_SetDrive(void* handle, Luna_Drive* drive)
{
	int status = 0;

	unsigned char buffer[17];

	// fill in the buffer
	buffer[0] = CMD_DRIVE;
	buffer[1] = drive->direction;
	buffer[2] = drive->right_speed;
	buffer[3] = drive->left_speed;

#if defined(USE_LIBUSB)
	// make the request for the drive
	status = libusb_control_transfer(devh, LIBUSB_ENDPOINT_OUT|LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE, HID_SET_REPORT,
		(HID_REPORT_TYPE_OUTPUT<<8)|CMD_DRIVE, 0, buffer, sizeof(buffer), TIMEOUT);
#endif

#if defined(USE_HIDAPI)
	status = hid_send_output_report(devh, buffer, sizeof(buffer));
#endif

	if (status >= 0)
	{
		// not much to do here
	}

	return status;
}

LUNA_DLL_API int Luna_Close(void* handle)
{
	int status = 0;

#if defined(USE_LIBUSB)
	libusb_release_interface(devh, 0);
	libusb_close(devh);
#endif

#if defined(USE_HIDAPI)
	hid_close(devh);
#endif

	return status;
}

LUNA_DLL_API int Luna_Uninit(void)
{
	int status = 0;

#if defined(USE_LIBUSB)
	libusb_exit(0);
#endif

#if defined(USE_HIDAPI)
#endif

	return status;
}
